package com.hup.joystick.service;

import com.hup.joystick.constant.DeviceKey.KeyActionType;
import com.hup.joystick.model.FunctionConfig;
import com.hup.joystick.model.QuickMenuFunction;
import com.hup.joystick.model.joystick.StandardEvent;
import com.hup.joystick.model.joystick.StandardEvent.StandardAction;
import com.hup.joystick.service.jna.DInputHResultException.HResult;
import com.hup.joystick.service.joyEvent.IJoyEventHandler;
import com.hup.joystick.service.joyEvent.KeyBoardFunctionHandler;
import com.hup.joystick.service.joyEvent.MouseFunctionHandler;
import com.hup.joystick.service.joyEvent.QuickMenuFunctionHandler;
import com.hup.joystick.service.joyEvent.VolumeMenuFunctionHandler;
import com.hup.utils.commons.ListUtil;
import com.hup.utils.commons.exception.CauseException;
import com.sun.jna.platform.WindowUtils;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HWND;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;

import java.awt.*;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * 实现手柄控制鼠键的服务类;
 * .匹配符合的功能配置
 * .处理按键事件[激活,释放]功能
 * 功能触发方式:
 * 按下时触发:无特殊说明的功能
 * 抬起时触发:快捷菜单[鼠标点击(功能,程序)]
 * [方向键,摇杆]未抬起时触发:鼠标移动
 *
 * @author hugan
 * @date 2020/10/31
 */
@Log4j2
@Component
public class JoyEventService {

    /**
     * 按序的事件处理器
     */
    private final ArrayList<IJoyEventHandler> lstHandler;
    private final QuickMenuFunctionHandler quickMenuFunctionHandler;
    /**
     * 每个程序的配置缓存:[key-程序路径,value-配置];
     * {@link #notifyConfigChanged(ArrayList)}
     */
    private final HashMap<String, FunctionConfig> mapConfigCache = new HashMap<>();

    public JoyEventService() {
        try {
            Robot robot = new Robot();
            lstHandler = new ArrayList<>();
            lstHandler.add(quickMenuFunctionHandler = new QuickMenuFunctionHandler(robot));
            lstHandler.add(new VolumeMenuFunctionHandler());
            lstHandler.add(new MouseFunctionHandler(robot));
            lstHandler.add(new KeyBoardFunctionHandler(robot));
        } catch (AWTException e) {
            throw new CauseException("鼠键控制器初始化异常:", e);
        }
    }

    /**
     * 配置[内容,顺序,是否启用]更新后,通知本来刷新缓存: {@link #mapConfigCache};
     * .key在notifyConfigChanged中全部初始化put
     * .key构成:
     * ...n个被配置的指定程序
     * ...1个null,代表通用的配置,除指定程序外的所有程序
     * .指定程序的value只在指定程序首次激活时构造对象,一开始是null
     * .通用配置的value,在本方法初始化
     */
    public void notifyConfigChanged(ArrayList<FunctionConfig> lstFunction) {
        mapConfigCache.clear();
        FunctionConfig mixedCommonConfig = new FunctionConfig();//合并后的通用配置
        mixedCommonConfig.setName("通用配置");
        mapConfigCache.put(null, mixedCommonConfig);

        ArrayList<FunctionConfig> lstCommonConfig = new ArrayList<>();//通用配置
        for (FunctionConfig config : lstFunction) {
            if (!config.enable) continue;
            if (config.lstApp.isEmpty()) {
                lstCommonConfig.add(config);
            } else {
                for (String app : config.getLstApp()) mapConfigCache.put(app, null);
            }
        }
        FunctionConfig.mixConfig(mixedCommonConfig, lstCommonConfig);
        log.debug("mixedCommonConfig={}", mixedCommonConfig);
    }

    /**
     * 处理按键事件;
     * .[一般按键事件,设备断开,程序监听错误的最后]都会调用此方法
     *
     * @param lstConfig notEmpty
     */
    public void handleJoyEvent(StandardEvent e, ArrayList<FunctionConfig> lstConfig) {
        //log.info("e={}, {}", e, e.currentPress);
        if (!HResult.succeeded(e.hr)) {
            HResult hResult = HResult.getResult(e.hr);
            log.info("hResult={}", hResult.chMsg);
            for (IJoyEventHandler handler : lstHandler) handler.releaseAllActiveFunction();
            return;
        }

        //处理[释放事件],不用解析配置:每个[按键释放事件]都通知所有处理器
        for (StandardAction action : e.actions) {
            if (action.actionType != KeyActionType.TYPE_RELEASED) continue;
            for (IJoyEventHandler handler : lstHandler) handler.handleRelease(e, action.standardKey);
        }

        /*
        处理[按下事件]:
        .匹配符合的[功能配置]
        .提取[按下事件列表newPress],可能会一开始就是空的!:仅有摇杆变化,无按下或抬起事件
         */
        String frontApp = getFrontApp();
        //log.info("frontApp={}", frontApp);
        FunctionConfig matchConfig = getMatchConfig(frontApp, lstConfig);
        if (matchConfig == null) {
            //app刚启动时,有控制事件,但config未加载完成,会导致找不到配置;
            //log.info("找不到配置", new Throwable());
            return;//不处理本次事件
        }
        ArrayList<StandardAction> newPress = ListUtil.getAll(e.actions, action -> action.actionType == KeyActionType.TYPE_PRESSED);

        //先给quickMenu处理
        boolean consumed;
        for (IJoyEventHandler handler : lstHandler) {
            consumed = handler.handlePressAndConsume(e, newPress, matchConfig);
            if (consumed) {
                //log.info("{},终止事件传递", handler.getClass().getSimpleName());
                //通知其他处理器释放所有功能:实际仅为了通知[鼠标移动处理器],因为它是通过handlePress进行[移动,停止]功能的
                for (IJoyEventHandler toNotify : lstHandler) {
                    if (toNotify != handler) toNotify.releaseAllActiveFunction();
                }
                return;
            }
        }

        //逐层传递[新按下事件]:处理器响应事件后,事件将从列表中被移除;
        for (IJoyEventHandler handler : lstHandler) {
            handler.handlePress(newPress, e.currentPress, matchConfig);
            //不能因为press为空就终止传递
        }

        //if (!newPress.isEmpty()) log.info("未处理={}", newPress);
    }

    private String getFrontApp() {
        HWND foregroundWindow = User32.INSTANCE.GetForegroundWindow();
        if (foregroundWindow == null) return null;//锁屏界面会返回null
        try {
            return WindowUtils.getProcessFilePath(foregroundWindow);
        } catch (Exception e) {
            log.warn("e={}, foregroundWindow={}", e.getMessage(), foregroundWindow);
            return null;
        }
    }

    /**
     * 获取符合要求的配置
     *
     * @return <pre>
     * 配置要求:
     * .无符合的独占配置时,返回无[生效程序]或 frontApp符合[生效程序]的配置
     * .有符合独占配置时,仅返回第一个符合的配置
     * .都不是禁用的配置
     * </pre>
     */
    private FunctionConfig getMatchConfig(String frontApp, ArrayList<FunctionConfig> lstConfig) {
        if (mapConfigCache.containsKey(frontApp)) {
            //当前程序是指定程序
            FunctionConfig target = mapConfigCache.get(frontApp);
            if (target != null) return target;

            FunctionConfig frontAppConfig = new FunctionConfig();//改app的配置,可以是独占或非独占的
            frontAppConfig.setName(frontApp);
            FunctionConfig.mixConfig(frontAppConfig, FunctionConfig.getMatchConfig(frontApp, lstConfig));
            mapConfigCache.put(frontApp, frontAppConfig);
            log.debug("frontAppConfig={}", frontAppConfig);
            return frontAppConfig;
        } else {
            //当前程序没有指定配置,使用通用配置
            return mapConfigCache.get(null);
        }
    }

    public void exeFunction(QuickMenuFunction function) {
        quickMenuFunctionHandler.exeFunction(function);
    }

    public void runApp(String filePath) {
        quickMenuFunctionHandler.runApp(filePath);
    }

}
